package com.kilomob.grady.client;

import io.netty.channel.Channel;

import java.util.List;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.kilomob.grady.client.netty.NettyChannelFactory;
import com.kilomob.grady.exception.GradyException;
import com.kilomob.grady.model.Request;
import com.kilomob.grady.model.Response;
import com.kilomob.grady.rpc.Invocation;
import com.kilomob.grady.rpc.Invoker;
import com.kilomob.grady.utils.AtomicPositiveInteger;

public class DefaultInvoker<T> implements Invoker<T>{
    private static Logger LOGGER = LoggerFactory.getLogger(DefaultInvoker.class);
    
    private final AtomicPositiveInteger index = new AtomicPositiveInteger();
    private Channel[] channels;
    private final Class<T>   type;
    
    private String clientId;
    private boolean isAsync;
    private boolean isOneway;
    private int timeout = 10*1000;
    
    
    public DefaultInvoker(Class<T> type, String clientId){
        this.clientId = clientId;
        this.type = type;
    }
    
    @Override
    public boolean isAvailable() {
        for (Channel client : channels){
            if (client.isOpen() && client.isActive()){
                return true ;
            }
        }
        return false;
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
    }

    @Override
    public Class<T> getInterface() {
        return type;
    }

    @Override
    public Object invoke(Invocation invocation) throws GradyException {
        Request request = new Request();
        request.setMessageId(UUID.randomUUID().toString());
        request.setClassName(this.type.getName());
        request.setMethodName(invocation.getMethodName());
        request.setTypeParameters(invocation.getParameterTypes());
        request.setParameters(invocation.getArguments());
        
        List<Channel> channels = NettyChannelFactory.getChannels(clientId);
        
        if(channels == null || channels.isEmpty()){
            LOGGER.warn("clientId may be config error or server may be shutdown !");
            throw new GradyException("clientId may be config error or server may be shutdown !");
        }
        
        
        Channel currentClient;
        if (channels.size() == 1) {
            currentClient = channels.get(0);
        } else {
            currentClient = channels.get(index.getAndIncrement() % channels.size());
        }
        try {
            if (isOneway) {
                currentClient.writeAndFlush(request);
                return new Response(request.getMessageId()).getResult();
            } else if (isAsync) {
                currentClient.writeAndFlush(request);
                return new Response(request.getMessageId()).getResult();
            } else {
                DefaultFuture defaultFuture = new DefaultFuture(currentClient,request,timeout);
                currentClient.writeAndFlush(request);
                try{
                    return defaultFuture.get().getResult();
                }catch(Exception e){
                    LOGGER.error("request error :",e);
                    return new Response(request.getMessageId()).getResult();
                }
            }
        } catch (Exception e) {
            throw new GradyException("Invoke remote method error. method: " + invocation.getMethodName(), e,"");
        }
        
    }

    public boolean isAsync() {
        return isAsync;
    }

    public void setAsync(boolean isAsync) {
        this.isAsync = isAsync;
    }

    public boolean isOneway() {
        return isOneway;
    }

    public void setOneway(boolean isOneway) {
        this.isOneway = isOneway;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }
    
}
